<?php

namespace esp\core;


final class Dispatcher
{
    private $request;
    private $response;
    public $debug;
    private $_Plugin = [];

    public function __construct(bool $emptyCache = false)
    {
        if (!defined('_MODULE')) exit('网站入口处须定义 _MODULE 项');
        if (!defined('_ROOT')) exit('网站入口处须定义 _ROOT 项，指向系统根目录');
        if (!class_exists('Yac')) exit('ESP框架必须安装有YAC缓存扩展');

        define('_DAY_TIME', strtotime(date('Ymd')));
        define('_CLI', (PHP_SAPI === 'cli' or php_sapi_name() === 'cli'));
        define('_IP', _CLI ? '127.0.0.1' : getenv('REMOTE_ADDR'));//客户端IP
        define('_DOMAIN', _CLI ? null : explode(':', getenv('HTTP_HOST') . ':')[0]);
        define('_HOST', _CLI ? null : host(_DOMAIN));//域名的根域
        define('_URI', _CLI ? ('/' . trim(implode('/', array_slice($GLOBALS["argv"], 1)), '/')) : parse_url(getenv('REQUEST_URI'), PHP_URL_PATH));
        chdir(_ROOT);

        if ($emptyCache) Config::empty();
        Config::formatSystem('frame');
        $this->request = new Request();
        $this->response = new Response($this->request);

        if (_CLI) $this->response->autoRun(false);
    }

    /**
     * 启用Debug，仅可用于在网站入口入明确调用
     * @return $this
     */
    public function debug(array $conf = [])
    {
        $dConf = Config::system('frame.debug');
        $this->debug = new Debug($this->request, $this->response, $conf + $dConf);
        return $this;
    }

    /**
     * 启用Error
     */
    public function error()
    {
        $eConf = Config::system('frame.error');
        $path = $eConf['path'] ?? 'error';
        new Mistake($this, intval($eConf['display'] ?? 1), function ($array) use ($path) {
            $info = [];
            $info['time'] = date('Y-m-d H:i:s');
            $info['host'] = getenv('HTTP_HOST');
            $info['url'] = _URI;
            $info['ip'] = getenv('REMOTE_ADDR');
            $info['agent'] = getenv('HTTP_USER_AGENT');
            $info['referer'] = getenv("HTTP_REFERER");
            if (!is_null($this->debug)) {
                $info['debug'] = $this->debug->error();
                $file = urlencode($info['debug']);
                $info['debug'] = "<a href='/tools/debug/{$file}'>{$info['debug']}</a>";
            }
            save_file(root($path) . date("/ERR_ymdHis_") . mt_rand() . '.txt', print_r($info, true) . "\n\n" . print_r($array, true));
        });
        return $this;
    }

    /**
     * @return $this
     */
    public function bootstrap()
    {
        if (!class_exists('Bootstrap')) {
            exit("Bootstrap类不存在，请检查/helper/Bootstrap.php文件");
        }
        $boot = new \Bootstrap();
        foreach (get_class_methods($boot) as $method) {
            if (stripos($method, '_init') === 0) {
                call_user_func_array([$boot, $method], [$this]);
            }
        }
        return $this;
    }

    /**
     * 接受注册插件
     * 或，返回已注册的插件
     * @param object $class
     */
    public function setPlugin($class)
    {
        $name = get_class($class);
        if (!is_subclass_of($class, Plugin::class)) {
            exit('插件' . $name . '须直接继承自\\esp\\core\\Plugin');
        }
        $name = ucfirst(substr($name, strrpos($name, '\\') + 1));
        if (isset($this->_Plugin[$name])) exit("插件名{$name}已被注册过");
        $this->_Plugin[$name] = &$class;
    }

    /**
     * 执行HOOK
     * @param $time
     */
    private function plugsHook(string $time)
    {
        if (empty($this->_Plugin)) return;
        if (!in_array($time, ['routeBefore', 'routeAfter', 'dispatchBefore', 'dispatchAfter', 'displayBefore', 'displayAfter', 'mainEnd'])) return;
        foreach ($this->_Plugin as &$plug) {
            if (method_exists($plug, $time)) {
                call_user_func_array([$plug, $time], [$this->request, $this->response]);
            }
        }
    }


    /**
     * @return Request
     */
    public function getRequest()
    {
        return $this->request;
    }

    /**
     * @return Response
     */
    public function getResponse()
    {
        return $this->response;
    }

    /**
     * 系统运行调度中心
     * @throws \Exception
     */
    public function run()
    {
        $this->plugsHook('routeBefore');//这个勾子必须运行
        (new Route($this->request));
        $this->getResponse()->getView()->dir($this->response->viewFolder());//重新注册视图目录
        $this->plugsHook('routeAfter');

        if (0 and !_CLI) $this->cache('display');

        $this->plugsHook('dispatchBefore');//这个勾子必须运行
        $this->dispatch();//运行控制器->方法
        $this->plugsHook('dispatchAfter');

        $this->plugsHook('displayBefore');
        if (!is_null($this->debug)) $this->debug->relay('ViewStar', []);
        $this->response->display();
        if (!is_null($this->debug)) $this->debug->relay('ViewStop', []);
        $this->plugsHook('displayAfter');

        if (!_CLI) fastcgi_finish_request();        //运行结束，客户端断开
        if (0 and !_CLI) $this->cache('save');
        $this->plugsHook('mainEnd');
    }

    private function cache(string $action)
    {
        static $cache;
        if (is_null($cache)) $cache = new Cache($this->request, $this->response);
        $action = 'cache' . ucfirst($action);
        $cache->{$action}();
    }

    /**
     * 路由结果分发至控制器动作
     */
    private function dispatch()
    {
        $actionExt = Config::system('frame.esp.actionExt');
        if (($this->request->method === 'POST') and ($p = Config::system('frame.esp.actionPost'))) $actionExt = $p;
        elseif ($this->request->isAjax() and ($p = Config::system('frame.esp.actionAjax'))) $actionExt = $p;

        $module = strtolower($this->request->module);
        $controller = ucfirst(strtolower($this->request->controller));
        $action = strtolower($this->request->action) . $actionExt;

        $debug = (defined('_DEBUG') and _DEBUG);

        if ($controller === 'Base') error($debug ? '控制器名不可以为base，这是系统保留公共控制器名' : 404);

        $base = $this->request->directory . "/{$module}/controllers/Base.php";
        $file = $this->request->directory . "/{$module}/controllers/{$controller}.php";
        if (is_readable($base)) load($base);//加载控制器公共类，有可能不存在
        if (!load($file)) error($debug ? "控制器[{$file}]不存在" : 404);

        $controller = $module . '\\' . $controller . 'Controller';// . Config::system('frame.esp.controlExt');
        $control = new $controller($this->_Plugin, $this->request, $this->response, $this->debug);
        if (!$control instanceof Controller) {
            error($debug ? "{$controller} 须继承自 esp\\core\\Controller" : 404);
        }

        if (!method_exists($control, $action) or !is_callable([$control, $action])) {
            error($debug ? "控制器[{$controller}]动作[{$action}]方法不存在或不可运行" : 400);
        }

        //运行初始化方法
        if (method_exists($control, '_init') and is_callable([$control, '_init'])) {
            call_user_func_array([$control, '_init'], []);
        }
        if (!is_null($this->debug)) $this->debug->relay('ControllerStar', []);
        $val = call_user_func_array([$control, $action], $this->request->params);
        if (!is_null($this->debug)) $this->debug->relay('ControllerStop', []);

        //运行结束方法
        if (method_exists($control, '_close') and is_callable([$control, '_close'])) {
            call_user_func_array([$control, '_close'], [$val]);
        }
        unset($control);
    }

    private function callIndex($module)
    {
        $controller = $module . '\\IndexController';
        $control = new $controller($this->_Plugin, $this->request, $this->response, $this->debug);
        if (!$control instanceof Controller) {
            error("{$controller} 须继承自 esp\\core\\Controller");
        }

    }


}